﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace SL5.Common
{
	/// <summary>
	/// Contains static methods for dealing with generic collections implementing the interfaces 
	/// <see cref="T:System.Collections.Generic.IList`1"/>,
	/// <see cref="T:System.Collections.Generic.ICollection`1"/>, 
	/// <see cref="T:System.Collections.Generic.IEnumerable`1"/>, 
	/// and <see cref="T:System.Collections.Generic.IDictionary`2"/>.
	/// </summary>
	public static class Collection
	{
		#region Public Static Methods 
		/// <summary>
		/// Performs a binary search on the specified <paramref name="collection"/> in search of the specified <paramref name="item"/>.
		/// </summary>
		/// <typeparam name="T">The type of the items in the collection.</typeparam>
		/// <param name="collection">The collection.</param>
		/// <param name="item">The item.</param>
		/// <param name="comparer">The comparer.</param>
		/// <returns>Index (or complement of where to insert such an item).</returns>
		public static int BinarySearch<T>(this IList<T> collection, T item, IComparer<T> comparer = null)
		{
			List<T> list = collection as List<T>;
			if (list != null)
			{
				return list.BinarySearch(item, comparer);
			}
			T[] array = collection as T[];
			return array != null ? Array.BinarySearch(array, item, comparer) : collection.BinarySearchByValue(item, value => value, comparer);
		}

		/// <summary>
		/// Performs a binary search on the specified <paramref name="collection"/> in search of an item with the specified <paramref name="value"/>.
		/// </summary>
		/// <typeparam name="T">The type of the items in the collection.</typeparam>
		/// <typeparam name="TValue">The type of the value.</typeparam>
		/// <param name="collection">The collection.</param>
		/// <param name="value">The value.</param>
		/// <param name="projection">The projection.</param>
		/// <param name="valueComparer">The value comparer.</param>
		/// <returns>Index (or complement of where to insert such an item).</returns>
		public static int BinarySearchByValue<T, TValue>(this IList<T> collection, TValue value, Func<T, TValue> projection, IComparer<TValue> valueComparer = null)
		{
			int startIndex = 0;
			if (collection != null && projection != null)
			{
				if (valueComparer == null)
				{
					valueComparer = Comparer<TValue>.Default;
				}
				int endIndex = collection.Count - 1;
				while (endIndex >= startIndex)
				{
					int midPoint = startIndex + (endIndex - startIndex) / 2;
					switch (Math.Sign(valueComparer.Compare(projection(collection[midPoint]), value)))
					{
						case -1:
							startIndex = midPoint + 1;
							break;
						case 0:
							return midPoint;
						case 1:
							endIndex = midPoint - 1;
							break;
					}
				}
			}
			return ~startIndex;
		}

		/// <summary>
		/// Finds the index in a <paramref name="collection"/> of the first item to match the <paramref name="predicate"/>.
		/// </summary>
		/// <typeparam name="T">The type of items in the collection.</typeparam>
		/// <param name="collection">The collection.</param>
		/// <param name="predicate">The predicate.</param>
		/// <returns>Index (or -1 if not found).</returns>
		public static int FindIndex<T>(this IList<T> collection, Func<T, bool> predicate)
		{
			if (collection != null && predicate != null)
			{
				for (int index = 0; index < collection.Count; index++)
				{
					if (predicate(collection[index]))
					{
						return index;
					}
				}
			}
			return -1;
		}

		/// <summary>
		/// Uses knowledge of the <paramref name="source"/> to attempt an optimization on the common paradigm of running a 
		/// projection on a collection and returning the results as an array.
		/// </summary>
		/// <typeparam name="T">The type of items in the collection.</typeparam>
		/// <typeparam name="TResult">The type of items in the result.</typeparam>
		/// <param name="source">The source.</param>
		/// <param name="selector">The selector.</param>
		/// <returns>Array.</returns>
		public static TResult[] SelectToArray<T, TResult>(this IEnumerable<T> source, Func<T, TResult> selector)
		{
			if (selector != null)
			{
				ICollection<T> collection = source as ICollection<T>;
				if (collection != null)
				{
					TResult[] array = new TResult[collection.Count];
					using (IEnumerator<T> enumerator = collection.GetEnumerator())
					{
						for (int index = 0; index < array.Length; index++)
						{
							enumerator.MoveNext();
							array[index] = selector(enumerator.Current);
						}
					}
					return array;
				}
			}
			return source.Select(selector).ToArray();
		}

		/// <summary>
		/// Uses knowledge of the <paramref name="source"/> to attempt an optimization on the common paradigm of running a 
		/// projection on a collection and returning the results as a list.
		/// </summary>
		/// <typeparam name="T">The type of items in the collection.</typeparam>
		/// <typeparam name="TResult">The type of items in the result.</typeparam>
		/// <param name="source">The source.</param>
		/// <param name="selector">The selector.</param>
		/// <returns>List.</returns>
		public static List<TResult> SelectToList<T, TResult>(this IEnumerable<T> source, Func<T, TResult> selector)
		{
			ICollection<T> collection = source as ICollection<T>;
			List<TResult> list = collection != null ? new List<TResult>(collection.Count) : new List<TResult>();
			list.AddRange(source.Select(selector));
			return list;
		}
		#endregion Public Static Methods 
	}
}